/***************************************************************************
 *   Copyright (C) 2009 by Geoffrey McRae   *
 *   geoff@spacevs.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#ifdef AUDIO_ALSA

#include "alsa.h"
#include <alsa/asoundlib.h>
#include <stdlib.h>
#include <stdio.h>

typedef struct {
	snd_pcm_t	*handle;
	unsigned int	channels;
	unsigned int	rate;
	bool		can_pause;
} audio_alsa_data_t;

void *mpr_audio_alsa_new(void *data) {
	audio_alsa_data_t *alsa;

	alsa = malloc(sizeof(audio_alsa_data_t));

	/* Get the card info */
	snd_ctl_t		*ctlHandle;
	snd_ctl_card_info_t	*ctlInfo;

	snd_ctl_card_info_alloca(&ctlInfo);
	if (snd_ctl_open(&ctlHandle, "default", 0) < 0) {
		ERR("Unable to open audio device \"%s\"", "default");
		free(alsa);
		return NULL;
	}

	if (snd_ctl_card_info(ctlHandle, ctlInfo) < 0) {
		snd_ctl_close(ctlHandle);
		ERR("Failed to get info for device \"%s\"", "default");
		free(alsa);
		return NULL;
	}

	INF("Found card: %s", snd_ctl_card_info_get_name(ctlInfo));
	snd_ctl_close(ctlHandle);

	return alsa;
}

void  mpr_audio_alsa_free(void* data) {
	free(data);
}

bool mpr_audio_alsa_open(void *data, unsigned int rate, unsigned int channels) {
	audio_alsa_data_t *alsa = data;

	int			err;
	snd_pcm_hw_params_t	*hw_params;

	if ((err = snd_pcm_open(&alsa->handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
		ERR("Unable to open audio device \"%s\" (%s)", "default", snd_strerror(err));
		return false;
	}

	if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
		ERR("Cannot allocate hardware parameter structure (%s)", snd_strerror(err));
		snd_pcm_close(alsa->handle);
		return false;
	}

	if ((err = snd_pcm_hw_params_any(alsa->handle, hw_params)) < 0) {
		ERR("Cannot initialize hardware parameter structure (%s)", snd_strerror(err));
		snd_pcm_close(alsa->handle);
		return false;
	}

	if ((err = snd_pcm_hw_params_set_access(alsa->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
		ERR("Cannot set access type (%s)", snd_strerror(err));
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(alsa->handle);
		return false;
	}

	if ((err = snd_pcm_hw_params_set_format(alsa->handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
		ERR("Cannot set sample format (%s)", snd_strerror(err));
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(alsa->handle);
		return false;
	}

	if ((err = snd_pcm_hw_params_set_rate_near(alsa->handle, hw_params, &rate, 0)) < 0) {
		ERR("Cannot set sample rate (%s)", snd_strerror(err));
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(alsa->handle);
		return false;
	}

	if ((err = snd_pcm_hw_params_set_channels(alsa->handle, hw_params, channels)) < 0) {
		ERR("Cannot set channel count (%s)", snd_strerror(err));
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(alsa->handle);
		return false;
	}

	if ((err = snd_pcm_hw_params(alsa->handle, hw_params)) < 0) {
		ERR("Cannot set parameteres (%s)", snd_strerror(err));
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(alsa->handle);
		return false;
	}

	if (!(alsa->can_pause = snd_pcm_hw_params_can_pause(hw_params)))
		WRN("%s", "Hardware can not pause");

	snd_pcm_hw_params_free(hw_params);

	if ((err = snd_pcm_prepare(alsa->handle)) < 0) {
		ERR("Cannot prepare audio interface for use (%s)", snd_strerror(err));
		snd_pcm_close(alsa->handle);
		return false;
	}

	alsa->rate	= rate;
	alsa->channels	= channels;

	INF("Opened audio device (rate = %d, channels = %d)", rate, channels);
	return true;
}

void mpr_audio_alsa_close(void *data) {
	audio_alsa_data_t *alsa = data;

	snd_pcm_close(alsa->handle);
}

double mpr_audio_alsa_getdelay(void *data) {
	audio_alsa_data_t *alsa = data;

	snd_pcm_sframes_t delay;
	snd_pcm_delay(alsa->handle, &delay);
	return (double)delay / (double)alsa->rate;
}

bool mpr_audio_alsa_write(void *data, int16_t *samples, unsigned int frames) {
	audio_alsa_data_t *alsa = data;

	int err;

	if ((err = snd_pcm_writei(alsa->handle, samples, frames)) != frames) {
		if (err == -EPIPE) {
			WRN("%s", "Broken pipe, possible buffer underrun, trying to recover");
			snd_pcm_drop	(alsa->handle);
			snd_pcm_prepare	(alsa->handle);
			if ((err = snd_pcm_writei(alsa->handle, samples, frames)) >= 0) {
				INF("%s", "Recovered");
				return true;
			}
		}

		ERR("Write to audio device failed (%s)", snd_strerror(err));
		return false;
	}

	return true;
}

void mpr_audio_alsa_pause(void *data) {
	audio_alsa_data_t *alsa = data;

	if (alsa->can_pause)
		snd_pcm_pause  (alsa->handle, 1);
	else {
		snd_pcm_drop   (alsa->handle);
		snd_pcm_prepare(alsa->handle);
	}
}

void mpr_audio_alsa_resume(void *data) {
	audio_alsa_data_t *alsa = data;

	if (alsa->can_pause) snd_pcm_pause(alsa->handle, 0);
}

#endif